package com.lagou.edu.factory;

import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.Repository;
import com.lagou.edu.annotation.Service;
import com.lagou.edu.annotation.Transactional;
import com.lagou.edu.pojo.ClassInfo;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;

/**
 * @author 应癫
 *
 * 工厂类，生产对象（使用反射技术）
 */
public class AnnotationBeanFactory {

    /**
     * 任务一：读取解析xml，获取需要扫描的package，将包中所有注解的类实例化
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     * 任务三：对外提供获取实例对象的接口（根据类型获取）
     */

    private static Map<ClassInfo,Object> map = new HashMap<>();  // 存储对象


    static {
        // 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
        // 加载xml
        InputStream resourceAsStream = AnnotationBeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        // 解析xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> beanList = rootElement.selectNodes("//annotation");
            // 这里默认限制xml中只有一个annotation节点
            Element element =  beanList.get(0);
            // 获取包扫描路劲
            String id = element.attributeValue("basePackage");
            // 获取该包下的所有class
            List<Class<?>> classList = getClasses(id);
            // 通过反射找到service,repository标注的类，并进行实例化
            // 一个类不能同时使用Service,Repository两个注解
            for (Class<?> aClass : classList) {
                // 如果当前类是接口，枚举，注解，跳过当前循环
                if(aClass.isAnnotation() || aClass.isEnum() || aClass.isInterface()){
                    continue;
                }
                ClassInfo classInfo = new ClassInfo();
                // 获取当前类的接口类型，存储时也是存接口类型
                // 如果没有接口，则用实现类类型存储
                Class<?> anInterface = null;
                Class<?>[] interfaces = aClass.getInterfaces();
                if(interfaces.length > 0){
                    anInterface = interfaces[0];
                }else{
                    anInterface = aClass;
                }
                classInfo.setClassName(aClass.getSimpleName());
                classInfo.setClassType(aClass);
                classInfo.setInterfaceType(anInterface);
                Service[] services = aClass.getAnnotationsByType(Service.class);
                // 获取配置的service value值
                String serviceValue = "";
                for (Service service : services) {
                    serviceValue = service.value();
                }
                Repository[] repositories = aClass.getAnnotationsByType(Repository.class);
                // 获取配置的repository value值
                String repositoryValue = "";
                for (Repository repository : repositories) {
                    repositoryValue = repository.value();
                }
                if(services.length > 0){
                    // 实例化该对象
                    Object o = aClass.newInstance();
                    if(!"".equals(serviceValue)){
                        classInfo.setClassName(serviceValue);
                    }
                    map.put(classInfo,o);
                }
                if(repositories.length > 0){
                    // 实例化该对象
                    Object o = aClass.newInstance();
                    if(!"".equals(repositoryValue)){
                        classInfo.setClassName(repositoryValue);
                    }
                    map.put(classInfo,o);
                }
            }


            // 实例化完成之后维护对象的依赖关系，检查哪些对象需要传值进入，根据它的配置，我们传入相应的值
            // 需要维护依赖关系的类已经存到了map中，这里直接遍历map
            for(Map.Entry entry:map.entrySet()){
                ClassInfo classInfo = (ClassInfo) entry.getKey();
                Object o = entry.getValue();
                Field[] fields = classInfo.getClassType().getDeclaredFields();
                for (Field field : fields) {
                    // 暴力破解，可以直接给private属性赋值
                    field.setAccessible(true);
                    // 找到该字段中配置的需要依赖注入配置信息
                    Autowired[] autowireds = field.getAnnotationsByType(Autowired.class);
                    if(autowireds.length > 0){
                        String autowiredValue = "";
                        for (Autowired autowired : autowireds) {
                            autowiredValue = autowired.value();
                        }
                        String fieldName = field.getName();
                        // 默认值，没有配置value信息，则按照字段名称注入
                        if("".equals(autowiredValue)){
                            field.set(o,getObjectByName(fieldName));
                        }else{
                            // 从map中取value对应的对象注入
                            field.set(o,getObjectByName(autowiredValue));
                        }
                    }
                }
            }

            Object proxy = null;
            // 依赖注入属性后，处理transactional事务
            // 为每个transactional注解标注的类创建代理类，并将
            for(Map.Entry entry:map.entrySet()){
                ClassInfo classInfo = (ClassInfo) entry.getKey();
                Object o = entry.getValue();
                Transactional[] annotationsByType = classInfo.getClassType().getAnnotationsByType(Transactional.class);
                if(annotationsByType.length > 0){
                    // 有Transactional注解的类，需要为其生成植入了事务的代理对象
                    // 判断有无interface
                    Class<?>[] interfaces = classInfo.getClassType().getInterfaces();
                    ProxyFactory proxyFactory = (ProxyFactory) getObjectByName("proxyFactory");
                    // 有接口，使用jdk动态代理
                    if(interfaces.length > 0){
                        proxy = proxyFactory.getJdkProxy(o);
                    }else{
                        // 无接口，使用cglib动态代理
                        proxy = proxyFactory.getCglibProxy(o);
                    }
                    // 将map中的service替换成代理类
                    map.put(classInfo,proxy);
                    // entry.setValue(proxy);
                }
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }

    public static Object getObjectByName(String name){
        Object result = null;
        for(Map.Entry entry : map.entrySet()){
            ClassInfo classInfo = (ClassInfo) entry.getKey();
            Object o = entry.getValue();
            if(classInfo.getClassName().equalsIgnoreCase(name)){
                result = o;
            }
        }
        return result;
    }

    /**
     * 从包package中获取所有的Class
     * @param packageName
     * @return
     */
    public static List<Class<?>> getClasses(String packageName){

        //第一个class类的集合
        List<Class<?>> classes = new ArrayList<Class<?>>();
        //是否循环迭代
        boolean recursive = true;
        //获取包的名字 并进行替换
        String packageDirName = packageName.replace('.', '/');
        //定义一个枚举的集合 并进行循环来处理这个目录下的things
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            //循环迭代下去
            while (dirs.hasMoreElements()){
                //获取下一个元素
                URL url = dirs.nextElement();
                //得到协议的名称
                String protocol = url.getProtocol();
                //如果是以文件的形式保存在服务器上
                if ("file".equals(protocol)) {
                    //获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    //以文件的方式扫描整个包下的文件 并添加到集合中
                    findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        return classes;
    }

    /**
     * 以文件的形式来获取包下的所有Class
     * @param packageName
     * @param packagePath
     * @param recursive
     * @param classes
     */
    public static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, List<Class<?>> classes){
        //获取此包的目录 建立一个File
        File dir = new File(packagePath);
        //如果不存在或者 也不是目录就直接返回
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }
        //如果存在 就获取包下的所有文件 包括目录
        File[] dirfiles = dir.listFiles(new FileFilter() {
            //自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
            public boolean accept(File file) {
                return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
            }
        });
        //循环所有文件
        for (File file : dirfiles) {
            //如果是目录 则继续扫描
            if (file.isDirectory()) {
                findAndAddClassesInPackageByFile(packageName + "." + file.getName(),
                        file.getAbsolutePath(),
                        recursive,
                        classes);
            }
            else {
                //如果是java类文件 去掉后面的.class 只留下类名
                String className = file.getName().substring(0, file.getName().length() - 6);
                try {
                    //添加到集合中去
                    classes.add(Class.forName(packageName + '.' + className));
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }



    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static  Object getBean(String id) {
        return getObjectByName(id);
    }

}
